﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UIT.Controls;
using UIT.Controls.Template;
using System.Reflection;
using System.IO;
#if !SILVERLIGHT
using System.Xaml;
using UIT.Animations;
#else
using System.Windows.Markup;
#endif

namespace UIT
{
    public class RendererManager<T, TRenderer>
        where TRenderer : IRenderer
    {
        private Action<TRenderer> initialize;
        private Dictionary<Type, Type> renderers = new Dictionary<Type, Type>();

        public RendererManager(Action<TRenderer> initialize)
        {
            this.initialize = initialize;
        }

        public void RegisterRenderer<U, V>()
            where U : IRenderer<V>, new()
            where V : T
        {
            renderers[typeof(V)] = typeof(U);
        }

        public TRenderer GetRenderer(T item, bool initialize = true)
        {
            Finder f = new Finder(this, initialize);
            f.Visit(item);
            return f.Renderer;
        }

        public IRenderer<U> GetRenderer<U>(U item, bool initialize = true)
            where U : T
        {
            if (item == null)
                return GetRenderer<U>(initialize);
            return (IRenderer<U>)GetRenderer(item.GetType(), initialize);
        }

        public IRenderer<U> GetRenderer<U>(bool initialize = true)
            where U : T
        {
            return (IRenderer<U>)GetRenderer(typeof(U), true);
        }

        public TRenderer GetRenderer(Type type, bool initialize = true)
        {
            Type rendererType;
            TRenderer renderer;
            if (!renderers.TryGetValue(type, out rendererType))
            {
                if (type.BaseType != typeof(object))
                    return GetRenderer(type.BaseType, initialize);
                return default(TRenderer);
            }
            else
                renderer = (TRenderer)Activator.CreateInstance(renderers[type]);
            if (initialize && this.initialize != null)
                this.initialize(renderer);
            return renderer;
        }

        public class Finder
        {
            private RendererManager<T, TRenderer> rendererManager;
            private bool initialize;

            public Finder(RendererManager<T, TRenderer> rendererManager, bool initialize)
            {
                this.rendererManager = rendererManager;
                this.initialize = initialize;
            }

            public void Visit(T visitable)
            {
                if (visitable == null)
                    return;
                else
                    Renderer = rendererManager.GetRenderer(visitable.GetType(), initialize);
            }

            public TRenderer Renderer { get; private set; }
        }

        public TRenderer Find(T item, bool initialize)
        {
            Finder f = new Finder(this, initialize);
            f.Visit(item);
            return f.Renderer;
        }
    }

    public class RendererManager
    {
        public RendererManager(Action<IRenderer> initialize)
            : this(initialize, initialize)
        {
        }

        public static RenderingMode Mode { get; set; }

        public RendererManager(Action<IControlRenderer> initialize)
            : this(initialize, null)
        {
        }

        public RendererManager(Action<ITemplateRenderer> initialize)
            : this(null, initialize)
        {
        }

        public RendererManager(Action<IControlRenderer> initializeControl, Action<ITemplateRenderer> initializeTemplate)
        {
            this.Control = new RendererManager<IControl, IControlRenderer>(initializeControl);
            this.Template = new RendererManager<ITemplate, ITemplateRenderer>(initializeTemplate);
            this.Animation = new RendererManager<IAnimation, IAnimationRenderer>(null);
        }

        public RendererManager<IControl, IControlRenderer> Control { get; private set; }
        public RendererManager<ITemplate, ITemplateRenderer> Template { get; private set; }
        public RendererManager<IAnimation, IAnimationRenderer> Animation { get; private set; }

        static List<Assembly> references = new List<Assembly>();

        public static event Action<List<Assembly>> ReferencesNeeded;

#if !SILVERLIGHT
        public static T CreateInstance<T>(Stream s, bool closeStream = true)
        {
            T instance = default(T);
            using (XamlReader reader = new XamlXmlReader(s))
            {
                instance = GetEmptyInstance<T>(reader);
                reader.Close();
            }
            s.Position = 0;
            return Load<T>(instance, s, closeStream);
        }
#else
        public static T CreateInstance<T>(Uri uri)
        {
            T instance = GetEmptyInstance<T>(uri);
            return Load<T>(instance, uri);
        }
#endif

        public static T Load<T>(T instance, string path)
        {
#if !SILVERLIGHT
            using (Stream file = File.OpenRead(path))
            {
                return Load<T>(instance, file);
            }
#else
            return Load<T>(instance, new Uri(path));
#endif
        }

#if !SILVERLIGHT
        public static T Load<T>(T instance, Stream s, bool closeStream = true)
        {
            using (XamlReader reader = new XamlXmlReader(s, XamlSchemaContext, new XamlXmlReaderSettings() { CloseInput = true, LocalAssembly = references[0] }))
            {
                XamlObjectWriter xamlObjectWriter = new XamlObjectWriter(reader.SchemaContext, new XamlObjectWriterSettings(XamlObjectWriterSettings) { RootObjectInstance = instance });
                switch (Mode)
                {
                    case RenderingMode.None:
                        throw new ArgumentException("The Rendering has to be set on the RendererManager");
                    case RenderingMode.Web:
                        XamlServices.Transform(new UitXamlReader(reader, "http://dragon-angel.fr/uit/wpf", "http://dragon-angel.fr/uit/sl"), xamlObjectWriter, true);
                        break;
                    case RenderingMode.WPF:
                        XamlServices.Transform(new UitXamlReader(reader, "http://dragon-angel.fr/uit/web", "http://dragon-angel.fr/uit/sl"), xamlObjectWriter, true);
                        break;
                    default:
                        break;
                }
                reader.Close();
                if (closeStream)
                    s.Close();
                return (T)xamlObjectWriter.Result;
            }
        }
#else
        public static T Load<T>(T instance, Uri uri)
        {
            System.Windows.Application.LoadComponent(instance, uri);
            return instance;
        }
#endif

#if !SILVERLIGHT
        private static void PostInitControl(object sender, XamlObjectEventArgs e)
        {
            ISupportLifeCycle item = e.Instance as ISupportLifeCycle;
            if (item != null)
                item.OnPostInit();
        }


        private static void PreInitControl(object sender, XamlObjectEventArgs e)
        {
            ISupportLifeCycle item = e.Instance as ISupportLifeCycle;
            if (item != null)
                item.OnPreInit();
        }

        public static T CreateInstance<T>(string path)
        {
            using (Stream file = File.OpenRead(path))
            {
                return CreateInstance<T>(file);
            }
        }
#endif

        public static Type GetType(string typeName)
        {
            Type t = Type.GetType(typeName, TypeNeeded == null);
            if (t == null)
                if (references.Count == 0 && ReferencesNeeded != null)
                    ReferencesNeeded(references);
            return TypeNeeded(typeName);
        }

        public static event Func<string, Type> TypeNeeded;


#if !SILVERLIGHT
        private static T GetEmptyInstance<T>(XamlReader reader)
        {
            bool readValue = false;
            while (reader.Read())
            {
                switch (reader.NodeType)
                {
                    case XamlNodeType.EndMember:
                        break;
                    case XamlNodeType.EndObject:
                        break;
                    case XamlNodeType.GetObject:
                        break;
                    case XamlNodeType.NamespaceDeclaration:
                        break;
                    case XamlNodeType.None:
                        break;
                    case XamlNodeType.StartMember:
                        readValue = reader.Member.PreferredXamlNamespace == "http://schemas.microsoft.com/winfx/2006/xaml" && reader.Member.Name == "Class";
                        break;
                    case XamlNodeType.StartObject:
                        break;
                    case XamlNodeType.Value:
                        if (readValue)
                            return (T)Activator.CreateInstance(GetType(reader.Value.ToString()));
                        break;
                    default:
                        break;
                }
            }
            return default(T);
        }
#else
        private static T GetEmptyInstance<T>(Uri uri)
        {
            using (System.Xml.XmlReader reader = System.Xml.XmlReader.Create(Application.GetResourceStream(uri)))
            {
                bool preventRead = false;
                while (preventRead || reader.Read())
                {
                    preventRead = false;
                    if (reader.NodeType == System.Xml.XmlNodeType.Element && reader.AttributeCount > 0)
                    {
                        reader.MoveToFirstAttribute();
                        do
                        {
                            if (reader.NodeType == System.Xml.XmlNodeType.Attribute && reader.NamespaceURI == "http://schemas.microsoft.com/winfx/2006/xaml" && reader.LocalName == "Class")
                                return (T)Activator.CreateInstance(GetType(reader.Value));
                            if (reader.NodeType != System.Xml.XmlNodeType.Attribute)
                            {
                                preventRead = true;
                                break;
                            }
                        }
                        while (reader.Read());
                    }
                }
            }

            return default(T);
        }
#endif

        private static XamlSchemaContext xamlSchemaContext;

        public static XamlSchemaContext XamlSchemaContext
        {
            get
            {
                if (xamlSchemaContext == null)
                {
                    if (references.Count == 0 && ReferencesNeeded != null)
                    {
                        ReferencesNeeded(references);
                    }
                    xamlSchemaContext = new XamlSchemaContext(references);
                }

                return xamlSchemaContext;
            }
        }

        private static XamlObjectWriterSettings xamlObjectWriterSettings;

        public static XamlObjectWriterSettings XamlObjectWriterSettings
        {
            get
            {
                if (xamlObjectWriterSettings == null)
                    xamlObjectWriterSettings = new XamlObjectWriterSettings { BeforePropertiesHandler = PreInitControl, AfterPropertiesHandler = PostInitControl };

                return xamlObjectWriterSettings;
            }
        }


    }
}
